-
Notifications
You must be signed in to change notification settings - Fork 12.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement Copy and Clone for UnsafeCell<T> #55207
Conversation
Only implemented if T implements Copy and/or Clone
r? @aidanhs (rust_highfive has picked a reviewer for you, use r? to override) |
The job Click to expand the log.
I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact |
The Travis build failed because I had a |
cc #25053 |
Thanks for the context @cramertj ! Open to new input, though it seems this is still in needs-decision land. I doubt my explanation will provide new clarity, but perhaps it will spark new discussion :) |
Ping from triage @aidanhs / @rust-lang/libs: This PR requires your review. |
The Similarly |
To restate the concern: adding these impls would imply that projecting
I'd also add that a slightly more conservative variant would be to only implement cc @RalfJung |
The reason we do this is that this is the only way to make I think in the abstract, this is fine.
This is possible in theory, but note that this applies almost any time we add a new trait impl, or really any safe operation for an existing type in libstd: In theory there might be code out there relying on this operation not to exist. The mere existence of this possibility shouldn't stop us. I am not sure if there is a good way to find out if this ever happened. |
In terms of motivation, what happened to the plan of |
Although we deprecated and then removed it (in favor of If projection form Changing the soundness requirements of an API that has been stable since Rust 1.0 is what makes me uncomfortable. However, having |
src/libcore/cell.rs
Outdated
#[stable(feature = "rust1", since = "1.0.0")] | ||
impl<T: Clone> Clone for UnsafeCell<T> { | ||
#[stable(feature = "unsafe_cell_copy_clone_conservative", since = "1.31.0")] | ||
impl<T: Clone + Copy> Clone for UnsafeCell<T> { | ||
fn clone(&self) -> UnsafeCell<T> { | ||
UnsafeCell::new(self.value.clone()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This shouldn't call clone()
, that is still unknown code executing.
It should make a Copy
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there an explict way to call copy? or is this acceptable?
#[stable(feature = "unsafe_cell_copy_clone_conservative", since = "1.31.0")]
impl<T: Clone + Copy> Clone for UnsafeCell<T> {
fn clone(&self) -> UnsafeCell<T> {
UnsafeCell::new(self.value)
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes that should do it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Together with a comment why we are not calling clone()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since Self: Copy
the body of this method can even be just *self
, though this is equivalent.
I've implemented the suggestion from @withoutboats, as this still addresses my original use case, and seems to have some consensus as being "more correct". Additionally, once the discussion on whether to do this is complete, I would be happy to add inline documentation that captures the points made in this thread, and what guarantees need to be made for correct usage of these new |
For users which expose let x = RefCell::new(2);
let mut y = x.borrow_mut();
let z: &mut u32 = &mut *y;
let copy = *x.as_unsafe_cell(); // reads from `z`, but not through `z` |
@alexcrichton Yeah, |
I think I’d prefer to introduce a new type (straw man name: |
Yes my point was the same as @SimonSapin how regardless of whether or not I agree with @SimonSapin as well though that we could create a new type for this, although I might be more partial to the name |
Yeah that name was not an actual proposal :) It was only intended as a way to refer to it by name rather than paraphrase for the purpose of this discussion. Maybe not have pub struct YoloCell<T: ?Sized> /* still not a serious name proposal */ {
pub value: T,
_private: (), // prevent construction by struct literal
}
impl<T: ?Sized> Copy for YoloCell<T> {}
unsafe impl<T: ?Sized> Sync for YoloCell<T> {}
impl<T> YoloCell<T> {
pub unsafe const fn new(value: T) -> Self {
Self { value, _private: () }
}
} Back to naming, I was thinking of this type as the (new) fundamental basis for interior mutability, and |
In your proposal, would I think at some point we have to figure out a general guidelines for how to handle things that are "accidentally safe". To what extend do we commit to guarantees that never really were meant as such? One consequence of our conservative approach to extending libstd and compiler functionality is that there will always be things that happen to be impossible at some point not because of a design decision, but because we didn't want to make the decision to make it possible yet. Thus many new features will technically be breaking changes, but only if unsafe code authors were "too aggressive" in exploiting "happy accidents" of the then-current libstd/rustc. Ironically, the more careful we are with new features, the more we draw ourselves into this corner! One example of that in the past would be the introduction of unions, which broke (as in "made unsound") some GC crates because they provided another way to not drop things. I see no fundamental difference between that situation and this, except that this time we actually have a realistic chance of avoiding the breakage. (Which is a good enough reason; if the policy is "we are fine breaking such accidental guarantees if maintaining them is not possible with reasonable effort while still advancing the language" that'd be a fine outcome.) As another example, consider the fact that This also includes implementing basically any trait for any type, because it is conceivable that unsafe code relies on certain traits to rule out certain types. (In fact this is exactly what happens here: Unsafe code, for no good reason other than "that's how it currently is", relied on |
FWIW at this point, this new cell type is just an idea and I’m trying to figure out "out loud" how it would work and what the consequences would be. I’m not claiming (yet) this is definitely the direction we should go. I think this is a valid concern in general, but I’m not convinced it applies here. I believe this aspect of (Emphasis added)
I read this as strongly implying “it only gives you a raw pointer”. |
Ping from triage! Could someone summarize how this PR can move forward? Does it require some team decision or FCP? Edit Or RFC? |
I think this is a longstanding enough conversation with enough fine details to get worked out that it should move into an RFC, even if just a small one. With that in mind, I'm going to close this PR. |
@jamesmunns We have an accepted RFC to allow repeated array initializer syntax with a non- In #20813 (comment) some have previously argued that |
That sounds like something diagnostics might be able to help with? |
Diagnostics happen with compiler errors, right? This is a case of a valid program having potentially unintended meaning. But it’s hard for the compiler to guess intent, and I assume that “use |
@SimonSapin yes, I believe this addresses my needs, and I think that #49147 is a better solution IMO. Thanks for following up! |
This change implements Copy and Clone for
UnsafeCell<T>
, ifT
implements Copy and/or Clone.I am opening this as a PR, rather than as an RFC, because I am unsure whether this falls under the category of "Nontrivial trait impl (because all trait impls are insta-stable)" or "Implementing an "obvious" trait like Clone/Debug/etc", and the guide suggests to do whatever is easier. I am happy to close this PR and write a full RFC, if necessary.
I believe this is sound, as
UnsafeCell<T>
does not provide public access toself.value
, and the accessor methodsget()
andset()
always refer to item contained within self. Therefore if the contained item can be cloned or copied, I believe the containing Cell should also be able to copied or cloned.The motivation for this change is to allow the use of
UnsafeCell<T>
in static arrays, using the[T; N]
syntax. For this syntax to work,T
must beCopy
. For example, if we wanted to make an array of[UnsafeCell<u16>; 128]
, it would be necessary to fully type out the initializer 128 times, which is not desirable, and difficult to do at global scope with a const_fn or macro.As of now, the
#[stable]
attribute is incorrect, and must be fixed before merging. I would also appreciate guidance on how to fill this out.